/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.security.web.context;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

/**
 默认情况下，在 Spring Security 过滤器链中，SecurityContextPersistenceFilter 是第二道防线，
 位于 WebAsyncManagerIntegrationFilter 之后。
 从 SecurityContextPersistenceFilter 这个过滤器的名字上就可以推断出来，它的作用是为了存储 SecurityContext 而设计的。
 整体上来说，SecurityContextPersistenceFilter 主要做两件事情：
 （1）当一个请求到来时，从 HttpSession 中获取 SecurityContext 并存入 SecurityContextHolder 中，
 这样在同一个请求的后续处理过程中，开发者始终可以通过 SecurityContextHolder获取到当前登录用户信息。
 （2）当一个请求处理完毕时，从 SecurityContextHolder 中获取 SecurityContext 并存入
 HttpSession 中（主要针对异步 Servlet），方便下一个请求到来时，再从 HttpSession 中拿出来使用，
 同时擦除 SecurityContextHolder 中的登录用户信息。

 PS:
 在 SecurityContextPersistenceFilter 过 滤器中， 当 一个请求处理完毕时， 从
 SecurityContextHolder 中获取 SecurityContext 存入 HttpSession 中，这一步的操作主要是针对
 异步 Servlet。如果不是异步 Servlet，在响应提交时，就会将 SecurityContext 保存到 HttpSession
 中了，而不会等到在 SecurityContextPersistenceFilter 过滤器中再去存储。
 *
 * @author Luke Taylor
 * @since 3.0
 */
public class SecurityContextPersistenceFilter extends GenericFilterBean {

	static final String FILTER_APPLIED = "__spring_security_scpf_applied";

	private SecurityContextRepository repo;

	private boolean forceEagerSessionCreation = false;

	public SecurityContextPersistenceFilter() {
		this(new HttpSessionSecurityContextRepository());
	}

	public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
		this.repo = repo;
	}

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		//（1）首先从 request 中获取 FILTER_APPLIED 属性，如果该属性值不为 null，则直接执
		//行 chain.doFilter 方法，当前过滤器到此为止，这个判断主要是确保该请求只执行一次该过滤
		//器。如果确实是该 request 第一次经过该过滤器，则给其设置上 FILTER_APPLIED 属性。
		if (request.getAttribute(FILTER_APPLIED) != null) {
			// ensure that filter is only applied once per request
			chain.doFilter(request, response);
			return;
		}

		final boolean debug = logger.isDebugEnabled();

		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

		//（2）forceEagerSessionCreation 变量表示是否要在过滤器链执行之前确保会话有效，由于
		//这是一个比较耗费资源的操作，因此默认为 false
		if (forceEagerSessionCreation) {
			HttpSession session = request.getSession();

			if (debug && session.isNew()) {
				logger.debug("Eagerly created session: " + session.getId());
			}
		}

		//（3）构造 HttpRequestResponseHolder 对象，将 HttpServletRequest 和 HttpServletResponse
		//都存储进去。
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
		//（4）调用 repo.loadContext 方法去加载 SecurityContext，repo 实际上就是我们前面所说
		//HttpSessionSecurityContextRepository 的实例，所以 loadContext 方法这里就不再赘述了。
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

		try {
			//（5）将读取到的 SecurityContext 存入 SecurityContextHolder 之中，这样，在接下来的处
			//理逻辑中，开发者就可以直接通过 SecurityContextHolder 获取当前登录用户对象了。
			SecurityContextHolder.setContext(contextBeforeChainExecution);

			//（6）调用 chain.doFilter 方法使请求继续向下走，但是要注意，此时传递的 request 和
			//response 对象是在 HttpSessionSecurityContextRepository 中封装后的对象，即 SaveToSession
			//ResponseWrapper 和 SaveToSessionRequestWrapper 的实例。
			chain.doFilter(holder.getRequest(), holder.getResponse());

		}
		finally {
			//（7）当请求处理完毕后，在 finally 模块中，获取最新的 SecurityContext 对象（开发者可
			//能在后续处理中修改了 SecurityContext 中的 Authentication 对象），然后清空 SecurityContextHolder 中的数据；
			// 再调用 repo.saveContext 方法保存 SecurityContext，具体的保存逻辑前面已
			//经说过，这里就不再赘述了。
			SecurityContext contextAfterChainExecution = SecurityContextHolder
					.getContext();
			// Crucial removal of SecurityContextHolder contents - do this before anything
			// else.
			SecurityContextHolder.clearContext();
			repo.saveContext(contextAfterChainExecution, holder.getRequest(),
					holder.getResponse());

			//（8）最后，从 request 中移除 FILTER_APPLIED 属性。
			request.removeAttribute(FILTER_APPLIED);

			if (debug) {
				logger.debug("SecurityContextHolder now cleared, as request processing completed");
			}
		}
	}

	public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
		this.forceEagerSessionCreation = forceEagerSessionCreation;
	}
}

/*
这就是整个 SecurityContextPersistenceFilter 过滤器的工作逻辑。一言以蔽之，请求在到达
SecurityContextPersistenceFilter 过滤器之后，先从 HttpSession 中读取 SecurityContext 出来，并
存入 SecurityContextHolder 之中以备后续使用；当请求离开 SecurityContextPersistenceFilter 过
滤器的时候，获取最新的 SecurityContext 并存入 HttpSession 中，同时清空 SecurityContextHolder
中的登录用户信息。
这就是第一种登录数据的获取方式，即从 SecurityContextHolder 中获取。

 */